#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <unistd.h>
#include <stdio.h>

#include <fdb.h>
#include <fdb-helpers.h>

static int callback_increase_int64t(query_t *query, void *ctx) {
	char *data;
	size_t size;
	helper_sequence_t *sequence;

	sequence = (helper_sequence_t*)ctx;
	query_data_get(query, 0, &data, &size);

	if (sequence->step > sequence->internal_step) {
		/*
		 * An extraordinarily large leap has been taken.
		 */
		sequence->internal_step = sequence->step;
	} else {
		sequence->internal_step *= 2;
	}

	sequence->value = (*(int64_t*)data) + sequence->step;
	sequence->internal_value = (*(int64_t*)data) + sequence->internal_step;

	sequence->internal_steps++;

	query_data_set(query, &sequence->internal_value, size,
	                      NULL, 0);
	return 0;
}

int helper_sequence_init(helper_sequence_t *sequence) {
	sequence->internal_steps = 0;
	sequence->internal_step = 0;
	return 0;
}

int helper_sequence_create_table(database_t *database, char *tablename) {
	table_t *table;

	table = table_new(tablename);
	if (table == NULL)
		return -1;
	table_add_column(table, column_new("value", "int64", NULL));
	table_add_column(table, column_new("name", "string", NULL));
	if (database_table_create(database, table) != 0) {
		table_destroy(table);
		return -1;
	}
	table_destroy(table);
	return 0;
}

int helper_sequence_create_sequence(transaction_t *transaction, char *tablename, helper_sequence_t *sequence) {
	query_t *query;
	result_e result;

	query = query_new(transaction);
	if (query == NULL)
		return -1;

	result = query_prepare(query, QUERY_INSERT, tablename);
	if (result_error(result)) {
		query_destroy(query);
		return -1;
	}

	result = query_execute(query);
	if (result_error(result)) {
		query_destroy(query);
		return -1;
	}

	query_data_set(query, &sequence->value, sizeof(sequence->value), sequence->name, strlen(sequence->name) + 1);

	result = query_step(query);
	if (result_error(result)) {
		query_destroy(query);
		return -1;
	}

	result = query_finalize(query);
	if (result_error(result)) {
		query_destroy(query);
		return -1;
	}

	query_destroy(query);

	return 0;
}

int helper_sequence_step_sequence(transaction_t *transaction, char *tablename, helper_sequence_t *sequence) {
	condition_t *condition;
	query_t *query;
	result_e result;

	if (sequence->internal_steps > 0) {
		if (sequence->value + sequence->step < sequence->internal_value) {
			sequence->value = sequence->value + sequence->step;
			return 0;
		}
	}

	query = query_new(transaction);
	if (query == NULL)
		return -1;

	result = query_prepare(query, QUERY_UPDATE, tablename);
	if (result_error(result)) {
		query_destroy(query);
		return -1;
	}
	
	condition = condition_predicate_new(1, "eq", 1, sequence->name, strlen(sequence->name) + 1);
	if (condition == NULL) {
		query_destroy(query);
		return -1;
	}

	query_append_conditions(query, condition, NULL);
	query_update_callback(query, callback_increase_int64t, sequence);

	result = query_execute(query);
	if (result_error(result)) {
		query_destroy(query);
		return -1;
	}

	result = query_finalize(query);
	if (result_error(result)) {
		query_destroy(query);
		return -1;
	}

	query_destroy(query);

	if (sequence->internal_steps > 100) {
		maintenance_vacuum_table(transaction, tablename);
		sequence->internal_steps = 1;
	}

	return 0;
}
